let StickStatus = {
  xPosition: 0,
  yPosition: 0,
  x: 0,
  y: 0,
  cardinalDirection: "C",
};

/**
 * @desc Principal object that draw a joystick, you only need to initialize the object and suggest the HTML container
 * @costructor
 * @param container {String} - HTML object that contains the Joystick
 * @param parameters (optional) - object with following keys:
 *  title {String} (optional) - The ID of canvas (Default value is 'joystick')
 *  width {Int} (optional) - The width of canvas, if not specified is setted at width of container object (Default value is the width of container object)
 *  height {Int} (optional) - The height of canvas, if not specified is setted at height of container object (Default value is the height of container object)
 *  internalFillColor {String} (optional) - Internal color of Stick (Default value is '#00AA00')
 *  internalLineWidth {Int} (optional) - Border width of Stick (Default value is 2)
 *  internalStrokeColor {String}(optional) - Border color of Stick (Default value is '#003300')
 *  externalLineWidth {Int} (optional) - External reference circonference width (Default value is 2)
 *  externalStrokeColor {String} (optional) - External reference circonference color (Default value is '#008000')
 *  autoReturnToCenter {Bool} (optional) - Sets the behavior of the stick, whether or not, it should return to zero position when released (Default value is True and return to zero)
 * @param callback {StickStatus} -
 */
var JoyStick = function (container, parameters, callback) {
  parameters = parameters || {};
  var title =
      typeof parameters.title === "undefined" ? "joystick" : parameters.title,
    width = typeof parameters.width === "undefined" ? 0 : parameters.width,
    height = typeof parameters.height === "undefined" ? 0 : parameters.height,
    internalFillColor =
      typeof parameters.internalFillColor === "undefined"
        ? "#87CEEBCC"
        : parameters.internalFillColor,
    internalLineWidth =
      typeof parameters.internalLineWidth === "undefined"
        ? 1
        : parameters.internalLineWidth,
    internalStrokeColor =
      typeof parameters.internalStrokeColor === "undefined"
        ? "#00BFFFCC"
        : parameters.internalStrokeColor,
    externalLineWidth =
      typeof parameters.externalLineWidth === "undefined"
        ? 2
        : parameters.externalLineWidth,
    externalStrokeColor =
      typeof parameters.externalStrokeColor === "undefined"
        ? "#87CEFA99"
        : parameters.externalStrokeColor,
    autoReturnToCenter =
      typeof parameters.autoReturnToCenter === "undefined"
        ? true
        : parameters.autoReturnToCenter;

  callback = callback || function (StickStatus) {};

  // Create Canvas element and add it in the Container object
  var objContainer = document.getElementById(container);

  // Fixing Unable to preventDefault inside passive event listener due to target being treated as passive in Chrome [Thanks to https://github.com/artisticfox8 for this suggestion]
  objContainer.style.touchAction = "none";

  var canvas = document.createElement("canvas");
  canvas.id = title;
  if (width === 0) {
    width = objContainer.clientWidth;
  }
  if (height === 0) {
    height = objContainer.clientHeight;
  }
  canvas.width = width;
  canvas.height = height;
  objContainer.appendChild(canvas);
  var context = canvas.getContext("2d");

  var pressed = 0; // Bool - 1=Yes - 0=No
  var circumference = 2 * Math.PI;
  var internalRadius = (canvas.width - (canvas.width / 2 + 10)) / 2;
  var maxMoveStick = internalRadius + 5;
  var externalRadius = internalRadius + 30;
  var centerX = canvas.width / 2;
  var centerY = canvas.height / 2;
  var directionHorizontalLimitPos = canvas.width / 10;
  var directionHorizontalLimitNeg = directionHorizontalLimitPos * -1;
  var directionVerticalLimitPos = canvas.height / 10;
  var directionVerticalLimitNeg = directionVerticalLimitPos * -1;
  // Used to save current position of stick
  var movedX = centerX;
  var movedY = centerY;

  // Check if the device support the touch or not
  if ("ontouchstart" in document.documentElement) {
    canvas.addEventListener("touchstart", onTouchStart, false);
    document.addEventListener("touchmove", onTouchMove, false);
    document.addEventListener("touchend", onTouchEnd, false);
  } else {
    canvas.addEventListener("mousedown", onMouseDown, false);
    document.addEventListener("mousemove", onMouseMove, false);
    document.addEventListener("mouseup", onMouseUp, false);
  }
  // Draw the object
  drawExternal();
  drawInternal();

  /******************************************************
   * Private methods
   *****************************************************/

  /**
   * @desc Draw the external circle used as reference position
   */
  function drawExternal() {
    context.beginPath();
    context.arc(centerX, centerY, externalRadius, 0, circumference, false);
    context.lineWidth = externalLineWidth;
    context.strokeStyle = externalStrokeColor;
    context.stroke();
  }

  /**
   * @desc Draw the internal stick in the current position the user have moved it
   */
  function drawInternal() {
    context.beginPath();
    let xDiff = movedX - centerX;
    let yDiff = movedY - centerY;
    let angel = Math.atan2(yDiff, xDiff);
    let dis = Math.min(Math.hypot(xDiff, yDiff), maxMoveStick);
    let drawX = Math.cos(angel) * dis + centerX;
    let drawY = Math.sin(angel) * dis + centerY;
    context.arc(drawX, drawY, internalRadius, 0, circumference, false);
    // create radial gradient
    var grd = context.createRadialGradient(
      centerX,
      centerY,
      5,
      centerX,
      centerY,
      200
    );
    // Light color
    grd.addColorStop(0, internalFillColor);
    // Dark color
    grd.addColorStop(1, internalStrokeColor);
    context.fillStyle = grd;
    context.fill();
    context.lineWidth = internalLineWidth;
    context.strokeStyle = internalStrokeColor;
    context.stroke();
  }

  /**
   * @desc Events for manage touch
   */
  let touchId = null;
  function onTouchStart(event) {
    pressed = 1;
    touchId = event.targetTouches[0].identifier;
  }

  function onTouchMove(event) {
    if (pressed === 1 && event.targetTouches[0].target === canvas) {
      movedX = event.targetTouches[0].pageX;
      movedY = event.targetTouches[0].pageY;
      // Manage offset
      var parent = canvas;
      while (parent.offsetParent.tagName.toUpperCase() !== "BODY") {
        parent = parent.offsetParent;
      }
      movedX -= parent.offsetLeft;
      movedY -= parent.offsetTop;
      //   if (canvas.offsetParent.tagName.toUpperCase() === "BODY") {
      //     movedX -= canvas.offsetLeft;
      //     movedY -= canvas.offsetTop;
      //   } else {
      //     movedX -= canvas.offsetParent.offsetLeft;
      //     movedY -= canvas.offsetParent.offsetTop;
      //   }
      // Delete canvas
      context.clearRect(0, 0, canvas.width, canvas.height);
      // Redraw object
      drawExternal();
      drawInternal();

      // Set attribute of callback
      StickStatus.xPosition = movedX;
      StickStatus.yPosition = movedY;
      StickStatus.x = (100 * ((movedX - centerX) / maxMoveStick)).toFixed();
      StickStatus.y = (
        100 *
        ((movedY - centerY) / maxMoveStick) *
        -1
      ).toFixed();
      StickStatus.cardinalDirection = getCardinalDirection();
      callback(StickStatus);
    }
  }

  function onTouchEnd(event) {
    if (event.changedTouches[0].identifier !== touchId) return;

    pressed = 0;
    // If required reset position store variable
    if (autoReturnToCenter) {
      movedX = centerX;
      movedY = centerY;
    }
    // Delete canvas
    context.clearRect(0, 0, canvas.width, canvas.height);
    // Redraw object
    drawExternal();
    drawInternal();

    // Set attribute of callback
    StickStatus.xPosition = movedX;
    StickStatus.yPosition = movedY;
    StickStatus.x = (100 * ((movedX - centerX) / maxMoveStick)).toFixed();
    StickStatus.y = (100 * ((movedY - centerY) / maxMoveStick) * -1).toFixed();
    StickStatus.cardinalDirection = getCardinalDirection();
    callback(StickStatus);
  }

  /**
   * @desc Events for manage mouse
   */
  function onMouseDown(event) {
    pressed = 1;
  }

  /* To simplify this code there was a new experimental feature here: https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/offsetX , but it present only in Mouse case not metod presents in Touch case :-( */
  function onMouseMove(event) {
    if (pressed === 1) {
      movedX = event.pageX;
      movedY = event.pageY;
      // Manage offset
      var parent = canvas;
      while (parent.offsetParent.tagName.toUpperCase() !== "BODY") {
        parent = parent.offsetParent;
      }
      movedX -= parent.offsetLeft;
      movedY -= parent.offsetTop;
      // Delete canvas
      context.clearRect(0, 0, canvas.width, canvas.height);
      // Redraw object
      drawExternal();
      drawInternal();

      // Set attribute of callback
      StickStatus.xPosition = movedX;
      StickStatus.yPosition = movedY;
      StickStatus.x = (100 * ((movedX - centerX) / maxMoveStick)).toFixed();
      StickStatus.y = (
        100 *
        ((movedY - centerY) / maxMoveStick) *
        -1
      ).toFixed();
      StickStatus.cardinalDirection = getCardinalDirection();
      callback(StickStatus);
    }
  }

  function onMouseUp(event) {
    pressed = 0;
    // If required reset position store variable
    if (autoReturnToCenter) {
      movedX = centerX;
      movedY = centerY;
    }
    // Delete canvas
    context.clearRect(0, 0, canvas.width, canvas.height);
    // Redraw object
    drawExternal();
    drawInternal();

    // Set attribute of callback
    StickStatus.xPosition = movedX;
    StickStatus.yPosition = movedY;
    StickStatus.x = (100 * ((movedX - centerX) / maxMoveStick)).toFixed();
    StickStatus.y = (100 * ((movedY - centerY) / maxMoveStick) * -1).toFixed();
    StickStatus.cardinalDirection = getCardinalDirection();
    callback(StickStatus);
  }

  function getCardinalDirection() {
    let result = "";
    let orizontal = movedX - centerX;
    let vertical = movedY - centerY;

    if (
      vertical >= directionVerticalLimitNeg &&
      vertical <= directionVerticalLimitPos
    ) {
      result = "C";
    }
    if (vertical < directionVerticalLimitNeg) {
      result = "N";
    }
    if (vertical > directionVerticalLimitPos) {
      result = "S";
    }

    if (orizontal < directionHorizontalLimitNeg) {
      if (result === "C") {
        result = "W";
      } else {
        result += "W";
      }
    }
    if (orizontal > directionHorizontalLimitPos) {
      if (result === "C") {
        result = "E";
      } else {
        result += "E";
      }
    }

    return result;
  }

  /******************************************************
   * Public methods
   *****************************************************/

  /**
   * @desc The width of canvas
   * @return Number of pixel width
   */
  this.GetWidth = function () {
    return canvas.width;
  };

  /**
   * @desc The height of canvas
   * @return Number of pixel height
   */
  this.GetHeight = function () {
    return canvas.height;
  };

  /**
   * @desc The X position of the cursor relative to the canvas that contains it and to its dimensions
   * @return Number that indicate relative position
   */
  this.GetPosX = function () {
    return movedX;
  };

  /**
   * @desc The Y position of the cursor relative to the canvas that contains it and to its dimensions
   * @return Number that indicate relative position
   */
  this.GetPosY = function () {
    return movedY;
  };

  /**
   * @desc Normalizzed value of X move of stick
   * @return Integer from -100 to +100
   */
  this.GetX = function () {
    return (100 * ((movedX - centerX) / maxMoveStick)).toFixed();
  };

  /**
   * @desc Normalizzed value of Y move of stick
   * @return Integer from -100 to +100
   */
  this.GetY = function () {
    return (100 * ((movedY - centerY) / maxMoveStick) * -1).toFixed();
  };

  /**
   * @desc Get the direction of the cursor as a string that indicates the cardinal points where this is oriented
   * @return String of cardinal point N, NE, E, SE, S, SW, W, NW and C when it is placed in the center
   */
  this.GetDir = function () {
    return getCardinalDirection();
  };
};
